use async_trait::async_trait;
use lazy_static::lazy_static;
use regex::Regex;
use std::sync::{Arc, LazyLock, Mutex};

use super::id_field::IdField;

lazy_static! {
    pub static ref REQUEST_MAP_URL_SINGLE_WILDCARD: String = String::from("/*");
    pub static ref REQUEST_MAP_URL_DOUBLE_WILDCARD: String = String::from("/**");
    pub static ref REQUEST_MAP_DENY_ALL: String = String::from("denyAll");
    pub static ref REQUEST_MAP_PERMIT_ALL: String = String::from("permitAll");
    pub static ref REQUEST_MAP_IS_AUTHENTICATED_PREFIX: String = String::from("isAuthenticated");
    pub static ref REQUEST_MAP_IS_AUTHENTICATED: String =
        REQUEST_MAP_IS_AUTHENTICATED_PREFIX.to_owned() + "()";
    pub static ref REQUEST_MAP_IS_FULLY_AUTHENTICATED_PREFIX: String =
        String::from("isFullyAuthenticated");
    pub static ref REQUEST_MAP_IS_FULLY_AUTHENTICATED: String =
        REQUEST_MAP_IS_FULLY_AUTHENTICATED_PREFIX.to_owned() + "()";
    pub static ref REQUEST_MAP_HAS_ROLE_PREFIX: String = String::from("hasRole");
    pub static ref REQUEST_MAP_HAS_ANY_ROLE_PREFIX: String = String::from("hasAnyRole");
    pub static ref REQUEST_MAP_HAS_NO_ROLE_PREFIX: String = String::from("hasNoRole");
    pub static ref REQUEST_MAP_HAS_NO_ANY_ROLE_PREFIX: String = String::from("hasNoAnyRole");
}

static CACHED_REQUEST_MAPS: LazyLock<Arc<Mutex<Vec<RequestMap>>>> =
    LazyLock::new(|| Arc::new(Mutex::new(Vec::new())));

#[derive(Debug)]
pub struct RequestMap {
    pub id: IdField,
    pub config_attribute: String,
    pub http_method: Option<String>,
    pub url: String,
}

#[async_trait]
pub trait RequestMapQuery: Send + Sync {
    async fn query_all(&self) -> Vec<RequestMap>;
}

pub async fn get_cached_request_maps(
    request_map_query: &impl RequestMapQuery,
) -> Arc<Mutex<Vec<RequestMap>>> {
    if CACHED_REQUEST_MAPS.lock().unwrap().len() == 0 {
        reload_cached_request_maps(request_map_query).await;
    }

    CACHED_REQUEST_MAPS.clone()
}

pub async fn reload_cached_request_maps(request_map_query: &impl RequestMapQuery) {
    *CACHED_REQUEST_MAPS.lock().unwrap() = request_map_query.query_all().await;
}

/// Returns the expression like hasRole('ROLE_ADMIN').
/// roleAuthority is the string of role authority. AKA role name.
pub fn request_map_has_role(role_authority: &str) -> String {
    request_map_build_expression_with_role(&REQUEST_MAP_HAS_ROLE_PREFIX, role_authority)
}

pub fn contains_any_role_in_config_attribute(
    role_names: Vec<&str>,
    config_attribute: &str,
) -> bool {
    contains_any_role_in_config_role_names(
        role_names,
        extract_role_names(config_attribute)
            .iter()
            .map(AsRef::as_ref)
            .collect(),
    )
}

/// Extracts role names from request map config attribute.
/// Example: hasRole('admin'), hasAnyRole('reporter', 'planner').
fn extract_role_names(config_attribute: &str) -> Vec<String> {
    let mut role_names: Vec<String> = vec![];

    // This would match the role names, like 'admin' or 'reporter', 'planner'.
    let pattern = format!(
        r"(?:{}|{}|{}|{})\((.+)\)",
        REQUEST_MAP_HAS_ROLE_PREFIX.as_str(),
        REQUEST_MAP_HAS_ANY_ROLE_PREFIX.as_str(),
        REQUEST_MAP_HAS_NO_ROLE_PREFIX.as_str(),
        REQUEST_MAP_HAS_NO_ANY_ROLE_PREFIX.as_str()
    );

    let re = Regex::new(&pattern).unwrap();
    let mut matches: Vec<&str> = vec![];

    for (_, [matched]) in re.captures_iter(config_attribute).map(|c| c.extract()) {
        matches.push(matched);
    }

    // If match found.
    if matches.len() > 0 {
        // Removes all single quotes.
        let mut matched_roles = matches[0].replace("'", "");

        // Removes all double quotes.
        matched_roles = matched_roles.replace("\"", "");

        // Splits the string by comma.
        let splited_roles = matched_roles.split(",");

        // Trims space for each role name.
        for role_name in splited_roles {
            role_names.push(role_name.trim().to_owned());
        }
    }

    role_names
}

/// Checks if configuredRoleNames contains any role in roleNames.
fn contains_any_role_in_config_role_names(
    role_names: Vec<&str>,
    config_role_names: Vec<&str>,
) -> bool {
    let mut contains_any_role = false;

    for role_name in role_names {
        for config_role_name in &config_role_names {
            if role_name.eq_ignore_ascii_case(config_role_name) {
                contains_any_role = true;
                break;
            }
        }
    }

    contains_any_role
}

// /// Returns the expression like hasAnyRole('ROLE_USER','ROLE_ADMIN').
// /// roleAuthorities is the list string of role authority. AKA role name.
// pub fn request_map_has_any_role(role_authorities: Vec<&str>) -> String {
//     request_map_build_expression_with_roles(&REQUEST_MAP_HAS_ANY_ROLE_PREFIX, role_authorities)
// }

// /// Returns the expression like hasNoRole('ROLE_ADMIN').
// /// roleAuthority is the string of role authority. AKA role name.
// pub fn request_map_has_no_role(role_authority: &str) -> String {
//     request_map_build_expression_with_role(&REQUEST_MAP_HAS_NO_ROLE_PREFIX, role_authority)
// }

// /// Returns the expression like hasNoAnyRole('ROLE_USER','ROLE_ADMIN').
// /// roleAuthorities is the list string of role authority. AKA role name.
// pub fn request_map_has_no_any_role(role_authorities: Vec<&str>) -> String {
//     request_map_build_expression_with_roles(&REQUEST_MAP_HAS_NO_ANY_ROLE_PREFIX, role_authorities)
// }

/// Returns the expression like hasRole('ROLE_ADMIN'), hasNoRole('ROLE_ADMIN').
/// roleAuthority is the string of role authority. AKA role name.
fn request_map_build_expression_with_role(prefix: &str, role_authority: &str) -> String {
    format!("{}('{}')", prefix, role_authority)
}

// /// Returns the expression like hasAnyRole('ROLE_USER','ROLE_ADMIN'), hasNoAnyRole('ROLE_USER','ROLE_ADMIN').
// /// roleAuthorities is the list string of role authority. AKA role name.
// fn request_map_build_expression_with_roles(prefix: &str, role_authorities: Vec<&str>) -> String {
//     // The initial string of the expression.
//     let mut result = format!("{}(", prefix);

//     // Goes through the list of roleAuthorities and appends it to result.
//     for authority in role_authorities {
//         result += &format!("'{}',", authority);
//     }

//     // Removes the last comma and closes with closing bracket.
//     result[0..result.len() - 1].to_owned() + ")"
// }
